1 /** 2 * Primitive aspects for an image 3 * 4 * This module is a submodule of devisualization.image. 5 * 6 * Provides determinance upon if a type is an image and respective optional functionality. 7 * 8 * License: 9 * Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017. 10 * Distributed under the Boost Software License, Version 1.0. 11 * (See accompanying file LICENSE_1_0.txt or copy at 12 * http://www.boost.org/LICENSE_1_0.txt) 13 */ 14 module devisualization.image.primitives; 15 import devisualization.image.interfaces; 16 import std.experimental.color; 17 import devisualization.util.core.memory.managed : managed; 18 import std.traits : isPointer, PointerTarget; 19 import stdx.allocator : IAllocator, theAllocator; 20 21 /** 22 * Determine if a type is an image. 23 * 24 * Use ImageStorage as the definition to compare against. 25 * 26 * See_Also: 27 * ImageStorage 28 */ 29 bool isImage(Image)() pure if (isPointer!Image && !__traits(compiles, {alias T = Image.PayLoadType;})) { 30 return isImage!(PointerTarget!Image)(); 31 } 32 33 /// 34 bool isImage(Image)() pure if (__traits(compiles, {alias T = Image.PayLoadType;})) { 35 return isImage!(Image.PayLoadType)(); 36 } 37 38 /// 39 bool isImage(Image)() pure if (!isPointer!Image && !__traits(compiles, {alias T = Image.PayLoadType;})) { 40 import std.traits : ReturnType, isIntegral, isUnsigned, Parameters; 41 import std.experimental.color : isColor; 42 43 static if (__traits(compiles, {Image image = Image.init;}) && is(Image == class) || is(Image == interface) || is(Image == struct)) { 44 // check that we can get the color type 45 static if (!__traits(compiles, {alias Color = ReturnType!(Image.getPixel);})) 46 return false; 47 else { 48 // make the color type easily worked on 49 alias Color = ReturnType!(Image.getPixel); 50 51 static if (!isColor!Color) 52 return false; 53 else { 54 static if (!__traits(compiles, { 55 Image image = void; 56 Color c = image.getPixel(0, 0); 57 image.setPixel(0, 0, c); 58 c = image[0, 0]; 59 image[0, 0] = c; 60 61 bool didResize = image.resize(2, 2); 62 })) { 63 // mutation, is it possible? 64 return false; 65 } else static if(!(isIntegral!(ReturnType!(Image.width)) && isUnsigned!(ReturnType!(Image.width)) && is(ReturnType!(Image.width) == ReturnType!(Image.height)))) { 66 // length must be unsigned integer (of some kind) 67 return false; 68 } else { 69 foreach(overload; __traits(getOverloads, Image, "getPixel")) { 70 alias Targs = Parameters!overload; 71 72 if (!(Targs.length == 2 && is(Targs[0] == Targs[1]) && isIntegral!(Targs[0]))) 73 return false; 74 } 75 76 // ok, is an image 77 return true; 78 } 79 } 80 } 81 } else { 82 return false; 83 } 84 } 85 86 version(unittest) private { 87 /* 88 * Tests isImage with a class that inherits from Image storage. 89 * Confirming that it works with that interface. 90 */ 91 92 class MyImageStorageUnit(Color) : ImageStorage!Color { 93 private { 94 const size_t width_, height_; 95 IAllocator allocator; 96 Color[][] data; 97 } 98 99 this(size_t width, size_t height, IAllocator allocator = theAllocator()) { 100 import stdx.allocator : makeArray; 101 this.allocator = allocator; 102 103 width_ = width; 104 height_ = height; 105 106 data = allocator.makeArray!(Color[])(width); 107 108 foreach(_; 0 .. width) { 109 data[_] = allocator.makeArray!Color(height); 110 } 111 } 112 113 @property { 114 size_t width() @nogc nothrow @trusted { return width_; } 115 size_t height() @nogc nothrow @trusted { return height_; } 116 } 117 118 Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; } 119 void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; } 120 Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); } 121 void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); } 122 123 bool resize(size_t, size_t) @nogc @safe { return false; } 124 } 125 126 struct NotAColor {} 127 128 // implicitly checks if it is an image 129 130 static assert(!__traits(compiles, { alias Type = MyImageStorageUnit!NotAColor; })); 131 static assert(__traits(compiles, { alias Type = MyImageStorageUnit!RGB8; })); 132 static assert(isImage!(MyImageStorageUnit!RGB8)); 133 134 // while we are at it, lets just test that the image color is gettable. 135 static assert(is(ImageColor!(MyImageStorageUnit!RGB8) == RGB8)); 136 137 /* 138 * Check if the class still works if it does not inherit from ImageStorage interface. 139 */ 140 141 class MyImageStorageUnitNoInheritance(Color) { 142 private { 143 const size_t width_, height_; 144 IAllocator allocator; 145 Color[][] data; 146 } 147 148 this(size_t width, size_t height, IAllocator allocator = theAllocator()) { 149 import stdx.allocator : makeArray; 150 this.allocator = allocator; 151 152 width_ = width; 153 height_ = height; 154 155 data = allocator.makeArray!(Color[])(width); 156 157 foreach(_; 0 .. width) { 158 data[_] = allocator.makeArray!Color(height); 159 } 160 } 161 162 @property { 163 size_t width() @nogc nothrow @trusted { return width_; } 164 size_t height() @nogc nothrow @trusted { return height_; } 165 } 166 167 Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; } 168 void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; } 169 Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); } 170 void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); } 171 172 bool resize(size_t, size_t) @nogc @safe { return false; } 173 } 174 175 // no implcit checks for if it is an image 176 177 static assert(!isImage!(MyImageStorageUnitNoInheritance!NotAColor)); 178 static assert(isImage!(MyImageStorageUnitNoInheritance!RGB8)); 179 180 // while we are at it, lets just test that the image color is gettable. 181 static assert(is(ImageColor!(MyImageStorageUnitNoInheritance!RGB8) == RGB8)); 182 183 /* 184 * Check if as a struct it still works. 185 */ 186 187 struct MyImageStorageUnitStruct(Color) { 188 private { 189 const size_t width_, height_; 190 IAllocator allocator; 191 Color[][] data; 192 } 193 194 this(size_t width, size_t height, IAllocator allocator = theAllocator()) { 195 import stdx.allocator : makeArray; 196 this.allocator = allocator; 197 198 width_ = width; 199 height_ = height; 200 201 data = allocator.makeArray!(Color[])(width); 202 203 foreach(_; 0 .. width) { 204 data[_] = allocator.makeArray!Color(height); 205 } 206 } 207 208 @property { 209 size_t width() @nogc nothrow @trusted { return width_; } 210 size_t height() @nogc nothrow @trusted { return height_; } 211 } 212 213 Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; } 214 void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; } 215 Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); } 216 void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); } 217 218 bool resize(size_t, size_t) @nogc @safe { return false; } 219 } 220 221 // no implcit checks for if it is an image 222 223 static assert(!isImage!(MyImageStorageUnitStruct!NotAColor)); 224 static assert(isImage!(MyImageStorageUnitStruct!RGB8)); 225 226 // while we are at it, lets just test that the image color is gettable. 227 static assert(is(ImageColor!(MyImageStorageUnitStruct!RGB8) == RGB8)); 228 } 229 230 /** 231 * The color type that an image storage type complies with. 232 */ 233 template ImageColor(Image) if (isImage!Image) { 234 import std.traits : ReturnType; 235 alias ImageColor = ReturnType!(Image.getPixel); 236 } 237 238 /** 239 * The type utilized as the main index for an image. 240 * 241 * Uses the first overload. 242 */ 243 template ImageIndexType(Image) if (isImage!Image) { 244 import std.traits : Parameters, isPointer; 245 246 // https://issues.dlang.org/show_bug.cgi?id=19170 247 static if (isPointer!Image) { 248 static assert(!isPointer!(typeof(Image.init[0]))); 249 alias ImageIndexType = Parameters!(__traits(getOverloads, typeof(Image.init[0]), "getPixel")[0])[0]; 250 } else { 251 alias ImageIndexType = Parameters!(__traits(getOverloads, Image, "getPixel")[0])[0]; 252 } 253 } 254 255 /** 256 * Determine if an image supports the pixel offset extension. 257 * 258 * Use ImageStorageOffset as the definition to compare against. 259 * 260 * See_Also: 261 * ImageStorage, ImageStorageOffset 262 */ 263 bool supportsImageOffset(Image)() if (isImage!Image) { 264 import std.traits : ReturnType; 265 266 static if (is(Image == class) || is(Image == struct)) { 267 Image image; 268 269 // make the color type easily worked on 270 271 alias Color = ReturnType!(Image.getPixel); 272 273 if (!__traits(compiles, { 274 size_t count = image.count; 275 })) { 276 // can we get access to basic elements of the image? 277 return false; 278 } else if (!__traits(compiles, { 279 Color c = image.getPixelAtOffset(0); 280 image.setPixelAtOffset(0, c); 281 c = image[0]; 282 image[0] = c; 283 })) { 284 // mutation, is it possible? 285 return false; 286 } else { 287 // ok, supports pixel offset 288 return true; 289 } 290 } else 291 return false; 292 } 293 294 version(unittest) private { 295 /* 296 * Tests supportsImageOffset with a class that inherits from Image storage. 297 * Confirming that it works with that interface. 298 */ 299 300 class MyImageStorageUnit3(Color) : ImageStorage!Color, ImageStorageOffset!Color { 301 private { 302 const size_t width_, height_; 303 IAllocator allocator; 304 Color[][] data; 305 } 306 307 this(size_t width, size_t height, IAllocator allocator = theAllocator()) { 308 import stdx.allocator : makeArray; 309 this.allocator = allocator; 310 311 width_ = width; 312 height_ = height; 313 314 data = allocator.makeArray!(Color[])(width); 315 316 foreach(_; 0 .. width) { 317 data[_] = allocator.makeArray!Color(height); 318 } 319 } 320 321 @property { 322 size_t width() @nogc nothrow @trusted { return width_; } 323 size_t height() @nogc nothrow @trusted { return height_; } 324 size_t count() @nogc nothrow @trusted { return width_ * height_; } 325 } 326 327 Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; } 328 void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; } 329 Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); } 330 void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); } 331 332 Color getPixelAtOffset(size_t offset) @nogc @trusted { return getPixel(offset % height_, offset / height_);} 333 void setPixelAtOffset(size_t offset, Color value) @nogc @trusted { setPixel(offset % height_, offset / height_, value); } 334 Color opIndex(size_t offset) @nogc @trusted { return getPixelAtOffset(offset); } 335 void opIndexAssign(Color value, size_t offset) @nogc @trusted { setPixelAtOffset(offset, value); } 336 337 bool resize(size_t, size_t) @nogc @safe { return false; } 338 } 339 340 // implicitly checks if it is an image 341 342 static assert(__traits(compiles, { alias Type = MyImageStorageUnit3!RGB8; })); 343 static assert(isImage!(MyImageStorageUnit3!RGB8)); 344 static assert(supportsImageOffset!(MyImageStorageUnit3!RGB8)); 345 346 /* 347 * Check if the class still works if it does not inherit from ImageStorage interface. 348 */ 349 350 class MyImageStorageUnit2NoInheritance(Color) { 351 private { 352 const size_t width_, height_; 353 IAllocator allocator; 354 Color[][] data; 355 } 356 357 this(size_t width, size_t height, IAllocator allocator = theAllocator()) { 358 import stdx.allocator : makeArray; 359 this.allocator = allocator; 360 361 width_ = width; 362 height_ = height; 363 364 data = allocator.makeArray!(Color[])(width); 365 366 foreach(_; 0 .. width) { 367 data[_] = allocator.makeArray!Color(height); 368 } 369 } 370 371 @property { 372 size_t width() @nogc nothrow @trusted { return width_; } 373 size_t height() @nogc nothrow @trusted { return height_; } 374 size_t count() @nogc nothrow @trusted { return width_ * height_; } 375 } 376 377 Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; } 378 void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; } 379 Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); } 380 void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); } 381 382 Color getPixelAtOffset(size_t offset) @nogc @trusted { return getPixel(offset % height_, offset / height_);} 383 void setPixelAtOffset(size_t offset, Color value) @nogc @trusted { setPixel(offset % height_, offset / height_, value); } 384 Color opIndex(size_t offset) @nogc @trusted { return getPixelAtOffset(offset); } 385 void opIndexAssign(Color value, size_t offset) @nogc @trusted { setPixelAtOffset(offset, value); } 386 387 bool resize(size_t, size_t) @nogc @safe { return false; } 388 } 389 390 // no implcit checks for if it is an image 391 392 static assert(__traits(compiles, { alias Type = MyImageStorageUnit2NoInheritance!RGB8; })); 393 static assert(isImage!(MyImageStorageUnit2NoInheritance!RGB8)); 394 static assert(supportsImageOffset!(MyImageStorageUnit2NoInheritance!RGB8)); 395 396 /* 397 * Check if as a struct it still works. 398 */ 399 400 struct MyImageStorageUnit2Struct(Color) { 401 private { 402 const size_t width_, height_; 403 IAllocator allocator; 404 Color[][] data; 405 } 406 407 this(size_t width, size_t height, IAllocator allocator = theAllocator()) { 408 import stdx.allocator : makeArray; 409 this.allocator = allocator; 410 411 width_ = width; 412 height_ = height; 413 414 data = allocator.makeArray!(Color[])(width); 415 416 foreach(_; 0 .. width) { 417 data[_] = allocator.makeArray!Color(height); 418 } 419 } 420 421 @property { 422 size_t width() @nogc nothrow @trusted { return width_; } 423 size_t height() @nogc nothrow @trusted { return height_; } 424 size_t count() @nogc nothrow @trusted { return width_ * height_; } 425 } 426 427 Color getPixel(size_t x, size_t y) @nogc @trusted { return data[x][y]; } 428 void setPixel(size_t x, size_t y, Color value) @nogc @trusted { data[x][y] = value; } 429 Color opIndex(size_t x, size_t y) @nogc @trusted { return getPixel(x, y); } 430 void opIndexAssign(Color value, size_t x, size_t y) @nogc @trusted { setPixel(x, y, value); } 431 432 Color getPixelAtOffset(size_t offset) @nogc @trusted { return getPixel(offset % height_, offset / height_);} 433 void setPixelAtOffset(size_t offset, Color value) @nogc @trusted { setPixel(offset % height_, offset / height_, value); } 434 Color opIndex(size_t offset) @nogc @trusted { return getPixelAtOffset(offset); } 435 void opIndexAssign(Color value, size_t offset) @nogc @trusted { setPixelAtOffset(offset, value); } 436 437 bool resize(size_t, size_t) @nogc @safe { return false; } 438 } 439 440 // no implcit checks for if it is an image 441 442 static assert(__traits(compiles, { alias Type = MyImageStorageUnit2Struct!RGB8; })); 443 static assert(isImage!(MyImageStorageUnit2Struct!RGB8)); 444 static assert(supportsImageOffset!(MyImageStorageUnit2Struct!RGB8)); 445 } 446 447 /** 448 * Is the type an input range that uses a PixelPoint as element type. 449 * 450 * See_Also: 451 * PixelPoint 452 */ 453 bool isPixelRange(T)() pure { 454 import std.range.interfaces : isInputRange, ElementType; 455 456 static if (isInputRange!T) { 457 alias ET = ElementType!T; 458 return __traits(hasMember, ET, "value") && is(ET == PixelPoint!(typeof(ET.value))) && isColor!(typeof(ET.value)); 459 } else 460 return false; 461 } 462 463 /// The pixel input range color type 464 template PixelRangeColor(T) if (isPixelRange!T) { 465 alias PixelColor = typeof((ElementType!T).value); 466 static assert(isColor!PixelColor); 467 } 468 469 /** 470 * Copies an image into another image 471 * 472 * Params: 473 * input = The input image 474 * destination = The output image 475 * 476 * Returns: 477 * The destination image for composibility reasons 478 */ 479 Image2 copyTo(Image1, Image2)(ref Image1 input, Image2 destination) @trusted if (is(Image1 == struct) && !is(Image2 == struct) && !isPointer!Image1 && isImage!Image1 && isImage!Image2) { 480 return copyTo(&input, destination); 481 } 482 483 /// Ditto 484 ref Image2 copyTo(Image1, Image2)(Image1 input, ref Image2 destination) @trusted if (is(Image2 == struct) && !isPointer!Image2 && isImage!Image1 && isImage!Image2) { 485 return *copyTo(input, &destination); 486 } 487 488 /// Ditto 489 Image2 copyTo(Image1, Image2)(Image1 input, Image2 destination) /+@nogc+/ @safe if (isImage!Image1 && isImage!Image2) { 490 assert(input.width <= destination.width); 491 assert(input.height <= destination.height); 492 493 foreach(x; 0 .. destination.width) { 494 foreach(y; 0 .. destination.height) { 495 destination.setPixel(x, y, input.getPixel(x, y)); 496 } 497 } 498 499 return destination; 500 } 501 502 /** 503 * Copies an pixel image range into an image 504 * 505 * Auto converts the color to the destination one. 506 * 507 * Params: 508 * input = The pixel input range 509 * destination = The output image 510 * 511 * Returns: 512 * The destination image for composibility reasons 513 */ 514 Image copyInto(IRRange, Image)(ref IRRange input, ref Image destination) @nogc @safe if (isPixelRange!IRRange && isImage!Image) { 515 alias Color = PixelRangeColor!IRRange; 516 517 foreach(pixel; input) { 518 destination.setPixel(pixel.x, pixel.y, pixel.value.convertTo!Color2); 519 } 520 521 return destination; 522 } 523 524 /** 525 * Copies an pixel image range into an image 526 * 527 * Params: 528 * input = The pixel input range 529 * destination = The output image 530 * 531 * Returns: 532 * The destination image for composibility reasons 533 */ 534 Image copyInto(IRRange, Image)(ref IRRange input, ref Image destination) @nogc @safe if (isPixelRange!IRRange && isImage!Image && is(ImageColor!Image == PixelRangeColor!(PixelRangeColor!IRRange))) { 535 foreach(pixel; input) { 536 destination.setPixel(pixel.x, pixel.y, pixel.value); 537 } 538 539 return destination; 540 } 541 542 /** 543 * Copies an pixel image range into a new image 544 * 545 * Params: 546 * input = The pixel input range 547 * destination = The output image that will be created, determines type to create as 548 * allocator = The allocator to allocate the new image by 549 * 550 * Returns: 551 * The pixel input range for composibility reasons 552 */ 553 IR createImageFrom(ImageImpl, IR)(IR input, out ImageImpl destination, IAllocator allocator=theAllocator()) @safe if (isImage!ImageImpl && isPixelRange!IR) { 554 import stdx.allocator : make; 555 556 size_t width = input.front.width; 557 size_t height = input.front.height; 558 559 destination = allocator.make!ImageImpl(width, height, allocator); 560 561 return input; 562 } 563 564 /** 565 * Copies an input ranges buffer into an image, ahead of time assigns all pixels to a specific color 566 * 567 * Params: 568 * input = The pixel input range 569 * destination = The output image that will be created, determines type to create as 570 * fillAs = The color to assign to each pixel 571 * 572 * See_Also: 573 * createImageFrom, copyInto, fillOn 574 * 575 * Returns: 576 * The destination image for composibility reasons 577 */ 578 Image assignTo(IR, Image, Color)(IR input, ref Image destination, Color fillAs) @nogc @safe if (isImage!ImageImpl && isPixelRange!IR && isColor!Color) { 579 return input.copyInto(destination.fillOn(fillAs)); 580 } 581 582 // TODO: unittests for copyInto, isPixelRange, PixelRangeColor, createImageFrom, assignTo